/*
Copyright (c) 2009, Luke Berndt
All rights reserved.
 
 * More Information: http://www.tagflo.com

Redistribution and use in source and binary forms, with or without modification, are permitted 
provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions 
  and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions 
  and the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of RoboTastic Software nor the names of its contributors may be used to endorse or 
  promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Data;
using System.Windows.Data;
using System.Data.Linq;
using Bindable;
using Bindable.Linq;
using System.Linq;
using System.Linq.Expressions;

namespace TagFlo
{
    public enum ComparisonType { LessThan = 0, GreaterThan, Equal, NotEqual, Contains };
    public enum FilterType { Tag = 0, Date, Path, PhotoSetName, Rating, ExposureTime, FocalLength, LensAperture, IsoSpeed, Width, Height, Subject };





    public class FilterSetting
    {
        private string filterComparison;
        private string filterValue;
        private string columnName;
        private string filterType;

        public string FilterComparison
        {
            get { return filterComparison; }
            set { filterComparison = value; }
        }

        public string FilterValue
        {
            get { return filterValue; }
            set { filterValue = value; }
        }

        public string FilterType
        {
            get { return filterType; }
            set { filterType = value; }
        }

        public string ColumnName
        {
            get { return columnName; }
            set { columnName = value; }
        }

        public FilterSetting(string name)
        {
            filterValue = "";
            filterComparison = "none";
            filterType = name;
            switch (name)
            {
                case "Photo Set":
                case "Photo Sets":
                    columnName = "PhotoSet";
                    break;
                case "ISO Speed":
                case "ISO":
                    columnName = "IsoSpeed";
                    break;
                case "Exposure Time":
                    columnName = "ExposureTime";
                    break;
                case "Aperture":
                case "Lens Aperture":
                    columnName = "LensAperture";
                    break;
                case "Focal Length":
                    columnName = "FocalLength";
                    break;
                case "Camera Model":
                    columnName = "CameraModel";
                    break;


                default:
                    columnName = name;
                    break;

            }

        }
       
    }


    class PhotoFilter
    {
        public Collection<FilterSetting> filters = new Collection<FilterSetting>();
        
        ThreadedObservableCollection<Photo> photoCollection;
        ThreadedObservableCollection<Path> pathCollection;
        ThreadedObservableCollection<Tag> tagCollection;
        ThreadedObservableCollection<PhotoSet> photoSetCollection;
        ThreadedObservableCollection<SmartSet> smartSetCollection;

        public PhotoFilter(MainWindow mainWindow)
        {
            photoCollection = mainWindow.photoCollection;
            pathCollection = mainWindow.pathCollection;
            tagCollection = mainWindow.tagCollection;
            photoSetCollection = mainWindow.photoSetCollection;
            smartSetCollection = mainWindow.smartSetCollection;


        }


        private string TagFilter(FilterSetting filterSetting)
        {
            
                switch (filterSetting.FilterComparison)
                {
                    case "is":
                        return "(Path IN (SELECT Path FROM TagTable WHERE Tag LIKE '" + filterSetting.FilterValue + "'))";
                    case "contains":
                        return "(Path IN (SELECT Path FROM TagTable WHERE Tag LIKE '" + filterSetting.FilterValue + "'))";
                    case "doesn't contain":
                        return "(Path NOT IN (SELECT Path FROM TagTable WHERE Tag LIKE '" + filterSetting.FilterValue + "'))";
                    case "starts with":
                        return "(Path IN (SELECT Path FROM TagTable WHERE Tag LIKE '" + filterSetting.FilterValue + "%'))";
                    case "ends with":
                        return "(Path IN (SELECT Path FROM TagTable WHERE Tag LIKE '%" + filterSetting.FilterValue + "'))";
                    case "are empty":
                        return "(Tags='')";
                    case "aren't empty":
                        return "(Tags!='')";
                    default:
                        return "";
                 }
            
        }

        public string TextFieldFilter(FilterSetting filterSetting)
        {
            switch (filterSetting.FilterComparison)
            {
                case "contains":
                    return "( " + filterSetting.ColumnName + " LIKE '" + filterSetting.FilterValue + "')";
                case "doesn't contain":
                    return "( " + filterSetting.ColumnName + " NOT LIKE '" + filterSetting.FilterValue + "')";
                case "starts with":
                    return "( " + filterSetting.ColumnName + " LIKE '" + filterSetting.FilterValue + "%')";
                case "ends with":
                    return "( " + filterSetting.ColumnName + " LIKE '%" + filterSetting.FilterValue + "')";
                case "is empty":
                    return "( " + filterSetting.ColumnName + "='')";
                case "is not empty":
                    return "( " + filterSetting.ColumnName + "!='')";
                default:
                    return "";
            }

        }

        public string PhotoSetNameFilter(FilterSetting filterSetting)
        {

            switch (filterSetting.FilterComparison)
            {
                case "is":
                    return "(Path IN (SELECT Path FROM PhotoSetTable WHERE PhotoSetName LIKE '" + filterSetting.FilterValue + "'))";
                case "is not":
                    return "(Path NOT IN (SELECT Path FROM PhotoSetTable WHERE PhotoSetName LIKE '" + filterSetting.FilterValue + "'))";
                case "starts with":
                    return "(Path IN (SELECT Path FROM PhotoSetTable WHERE PhotoSetName LIKE '" + filterSetting.FilterValue + "%'))";
                case "ends with":
                    return "(Path IN (SELECT Path FROM PhotoSetTable WHERE PhotoSetName LIKE '%" + filterSetting.FilterValue + "'))";
            }
            
            string s = "";

            s = " (  Path IN " + filterSetting.FilterValue + " ) ";

            return s;

        }

        public string DateFilter(FilterSetting filterSetting)
        {
            string s = "";

            s = " " + filterSetting.ColumnName + "=" + filterSetting.FilterValue + " ";

            return s;
        }

        public string YearFilter(FilterSetting filterSetting)
        {
            string s = "";

            s = "( datename(Year, DateTaken)  = " + filterSetting.FilterValue + " )";
            return s;
        }

        public string MonthFilter(FilterSetting filterSetting)
        {
            string s = "";

            s = "( datename(m, DateTaken)  = '" + filterSetting.FilterValue + "' )";
            return s;
        }

        public string DayFilter(FilterSetting filterSetting)
        {
            string s = "";

            s = "( datename(Day, DateTaken)  = " + filterSetting.FilterValue + " )";
            return s;
        }

        public string PathFilter(FilterSetting filterSetting)
        {
            string s = "";

            s = " ( " + filterSetting.ColumnName + " LIKE '" + filterSetting.FilterValue + "%' ) ";

            return s;
        }

       

        public string ColumnFilter(FilterSetting filterSetting)
        {
            string s = "";
            switch (filterSetting.FilterComparison)
            {
                case "contains":
                    s = " ( " + filterSetting.ColumnName + " LIKE '" + filterSetting.FilterValue + "') ";
                    break;

                case "is greater than":
                    s = " ( " + filterSetting.ColumnName + " > '" + filterSetting.FilterValue + "') ";

                    break;
                case "is less than":
                    s = " ( " + filterSetting.ColumnName + " < '" + filterSetting.FilterValue + "') ";

                    break;
                case "is greater than or equal to":
                    s = " ( " + filterSetting.ColumnName + " >= '" + filterSetting.FilterValue + "') ";

                    break;
                case "is less than or equal to":
                    s = " ( " + filterSetting.ColumnName + " <= '" + filterSetting.FilterValue + "') ";

                    break;

                case "is not":

                    s = " ( " + filterSetting.ColumnName + " != '" + filterSetting.FilterValue + "') ";

                    break;
                case "is":

                    s = " ( " + filterSetting.ColumnName + " = '" + filterSetting.FilterValue + "') ";

                    break;
            }

            return s;
        }

        public List<Photo> IsoSpeedFilter(FilterSetting filterSetting, List<Photo> collection)
        {
            List<Photo> returnCollection = null;

            switch (filterSetting.FilterComparison)
            {
                case "is greater than":
                    returnCollection = (from p in collection.AsBindable() where p.IsoSpeed > (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is less than":
                    returnCollection = (from p in collection.AsBindable() where p.IsoSpeed < (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is greater than or equal to":
                    returnCollection = (from p in collection.AsBindable() where p.IsoSpeed >= (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is less than or equal to":
                    returnCollection = (from p in collection.AsBindable() where p.IsoSpeed <= (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is not":
                    returnCollection = (from p in collection.AsBindable() where p.IsoSpeed != (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is":
                    returnCollection = (from p in collection.AsBindable() where p.IsoSpeed == (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;
            }

            return returnCollection;
        }




        public List<Photo> FocalLengthFilter(FilterSetting filterSetting, List<Photo> collection)
        {
            List<Photo> returnCollection = null;

            switch (filterSetting.FilterComparison)
            {
                case "is greater than":
                    returnCollection = (from p in collection.AsBindable() where p.FocalLength > (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is less than":
                    returnCollection = (from p in collection.AsBindable() where p.FocalLength < (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is greater than or equal to":
                    returnCollection = (from p in collection.AsBindable() where p.FocalLength >= (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is less than or equal to":
                    returnCollection = (from p in collection.AsBindable() where p.FocalLength <= (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is not":
                    returnCollection = (from p in collection.AsBindable() where p.FocalLength != (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is":
                    returnCollection = (from p in collection.AsBindable() where p.FocalLength == (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;
            }

            return returnCollection;
        }


        public List<Photo> ExposureTimeFilter(FilterSetting filterSetting, List<Photo> collection)
        {
            List<Photo> returnCollection = null;

            switch (filterSetting.FilterComparison)
            {
                case "is greater than":
                    returnCollection = (from p in collection.AsBindable() where p.ExposureTime > (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is less than":
                    returnCollection = (from p in collection.AsBindable() where p.ExposureTime < (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is greater than or equal to":
                    returnCollection = (from p in collection.AsBindable() where p.ExposureTime >= (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is less than or equal to":
                    returnCollection = (from p in collection.AsBindable() where p.ExposureTime <= (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is not":
                    returnCollection = (from p in collection.AsBindable() where p.ExposureTime != (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is":
                    returnCollection = (from p in collection.AsBindable() where p.ExposureTime == (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;
            }

            return returnCollection;
        }


        public List<Photo> LensApertureFilter(FilterSetting filterSetting, List<Photo> collection)
        {
            List<Photo> returnCollection = null;

            switch (filterSetting.FilterComparison)
            {
                case "is greater than":
                    returnCollection = (from p in collection.AsBindable() where p.LensAperture > (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is less than":
                    returnCollection = (from p in collection.AsBindable() where p.LensAperture < (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is greater than or equal to":
                    returnCollection = (from p in collection.AsBindable() where p.LensAperture >= (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is less than or equal to":
                    returnCollection = (from p in collection.AsBindable() where p.LensAperture <= (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is not":
                    returnCollection = (from p in collection.AsBindable() where p.LensAperture != (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;

                case "is":
                    returnCollection = (from p in collection.AsBindable() where p.LensAperture == (int.Parse(filterSetting.FilterValue)) select p).ToList();
                    break;
            }

            return returnCollection;
        }



        public Expression<Func<Photo, bool>> MonthFilter(FilterSetting filterSetting, Expression<Func<Photo, bool>> photoFilter)
        {
            int month = int.Parse(filterSetting.FilterValue);
            switch (filterSetting.FilterComparison)
            {
                case "is":
                    photoFilter = photoFilter.And(p => p.DateTaken.Value.Month == month);
                    break;
            }

            return photoFilter;
        }

        public Expression<Func<Photo, bool>> DayFilter(FilterSetting filterSetting, Expression<Func<Photo, bool>> photoFilter)
        {
            int day = int.Parse(filterSetting.FilterValue);
            switch (filterSetting.FilterComparison)
            {
                case "is":
                    photoFilter = photoFilter.And(p => p.DateTaken.Value.Day == day);
                    break;
            }

            return photoFilter;
        }


        public Expression<Func<Photo, bool>> YearFilter(FilterSetting filterSetting, Expression<Func<Photo, bool>> photoFilter)
        {
            int year = int.Parse(filterSetting.FilterValue);
            switch (filterSetting.FilterComparison)
            {
                case "is":
                    photoFilter = photoFilter.And(p => p.DateTaken.Value.Year == year);
                    break;
            }

            return photoFilter;
        }


        public Expression<Func<Photo, bool>> PhotoSetFilter(FilterSetting filterSetting, Expression<Func<Photo, bool>> photoFilter)
        {
            string photoSetName = filterSetting.FilterValue;
            switch (filterSetting.FilterComparison)
            {
                case "is":
                    photoFilter = photoFilter.And(p => p.PhotoSets.Any(ps => ps.PhotoSetName == photoSetName));
                    break;
            }

            return photoFilter;
        }

        public Expression<Func<Photo, bool>> TagFilter(FilterSetting filterSetting, Expression<Func<Photo, bool>> photoFilter)
        {
           string tag = filterSetting.FilterValue;
            switch (filterSetting.FilterComparison)
            {
                case "is":
                    photoFilter = photoFilter.And(p => p.Tags.Any(t => t.TagName == tag));
                    break;
            }

            return photoFilter;
        }


        public Expression<Func<Photo, bool>> PathFilter(FilterSetting filterSetting, Expression<Func<Photo, bool>> photoFilter)
        {
            List<int> idList = new List<int>(Array.ConvertAll(
        filterSetting.FilterValue.Split(','), new Converter<string, int>(int.Parse)));
            
            switch (filterSetting.FilterComparison)
            {
                case "in":
                    photoFilter = photoFilter.And(p => idList.Contains(p.Paths.PathID));
                    break;
            }

            return photoFilter;
        }


        public Expression<Func<Photo, bool>> StringFilter(FilterSetting filterSetting, Expression<Func<Photo, bool>> photoFilter)
        {
           
            BinaryExpression filter = null;
            ConstantExpression value = Expression.Constant(filterSetting.FilterValue);
            ParameterExpression entity = Expression.Parameter(typeof(Photo), "p");
            Expression parameter = LambdaExpression.PropertyOrField(entity, filterSetting.ColumnName);
            parameter = Expression.Convert(parameter, filterSetting.FilterValue.GetType());

            switch (filterSetting.FilterComparison)
            {
                case "is greater than":
                    filter = Expression.GreaterThan(parameter, value);
                    break;

                case "is less than":
                    filter = Expression.LessThan(parameter, value);
                    break;

                case "is greater than or equal to":
                    filter = Expression.GreaterThanOrEqual(parameter, value);
                    break;

                case "is less than or equal to":
                    filter = Expression.LessThanOrEqual(parameter, value);
                    break;

                case "is not":
                    filter = Expression.NotEqual(parameter, value);
                    break;

                case "is":
                    filter = Expression.Equal(parameter, value);
                    break;
                
            }
            var exp = Expression.Lambda<Func<Photo, bool>>(filter, new ParameterExpression[] { entity });
            photoFilter = photoFilter.And(exp);
            string evalS = exp.ToString();
            return photoFilter;
        }


        public Expression<Func<Photo, bool>> IntFilter(FilterSetting filterSetting, Expression<Func<Photo, bool>> photoFilter)
        {
            int i = (int.Parse(filterSetting.FilterValue));

            BinaryExpression filter = null;
            ConstantExpression value = Expression.Constant(i);
            ParameterExpression entity = Expression.Parameter(typeof(Photo), "p");
            Expression parameter = LambdaExpression.PropertyOrField(entity, filterSetting.ColumnName);
            parameter = Expression.Convert(parameter, i.GetType());

            switch (filterSetting.FilterComparison)
            {
                case "is greater than":
                    filter = Expression.GreaterThan(parameter, value);
                    break;

                case "is less than":
                    filter = Expression.LessThan(parameter, value);
                    break;

                case "is greater than or equal to":
                    filter = Expression.GreaterThanOrEqual(parameter, value);
                    break;

                case "is less than or equal to":
                    filter = Expression.LessThanOrEqual(parameter, value);
                    break;

                case "is not":
                    filter = Expression.NotEqual(parameter, value);
                    break;

                case "is":
                    filter = Expression.Equal(parameter, value);
                    break;
            }
            var exp = Expression.Lambda<Func<Photo, bool>>(filter, new ParameterExpression[] { entity });
            photoFilter = photoFilter.And(exp);
            string evalS = exp.ToString();
            return photoFilter;
        }



            
        public  Expression<Func<Photo,bool>> ShortFilter (FilterSetting filterSetting, Expression<Func<Photo,bool>> photoFilter)
        {
            short s = (short.Parse(filterSetting.FilterValue));
            
            BinaryExpression filter=null;
            ConstantExpression value = Expression.Constant(s);
            ParameterExpression entity = Expression.Parameter(typeof(Photo), "p");
            Expression parameter = LambdaExpression.PropertyOrField(entity, filterSetting.ColumnName);
            parameter = Expression.Convert(parameter, s.GetType());
    
            switch (filterSetting.FilterComparison)
            {
                case "is greater than":
                    filter = Expression.GreaterThan(parameter, value);
                    break;
             
                case "is less than":
                    filter = Expression.LessThan(parameter, value);
                    break;
                
                case "is greater than or equal to":
                    filter = Expression.GreaterThanOrEqual(parameter, value);
                    break;
                
                case "is less than or equal to":
                    filter = Expression.LessThanOrEqual(parameter, value);
                    break;

                case "is not":
                    filter = Expression.NotEqual(parameter, value);
                    break;
         
                case "is":
                     filter = Expression.Equal(parameter,value);
                    break;
            }
            var exp = Expression.Lambda<Func<Photo, bool>>(filter, new ParameterExpression[] { entity });
            photoFilter = photoFilter.And(exp);
            string evalS = exp.ToString();
            return photoFilter;
        }

        public List<Photo> FilterCollection()
        {
           var photoFilter = PredicateBuilder.True<Photo>();
    
            foreach (FilterSetting filter in filters)
            {
                switch (filter.ColumnName)
                {
                    case "Rating":
                        photoFilter = ShortFilter(filter, photoFilter);
                        break;


                  
                    case "IsoSpeed":
                    case "ExposureTime":
                    case "LensAperture":
                    case "FocalLength":
                         photoFilter = IntFilter(filter, photoFilter);
                        
                        break;

                    case "PhotoSet":
                        photoFilter = PhotoSetFilter(filter, photoFilter);

                        break;
                    case "Year":
                        photoFilter = YearFilter(filter, photoFilter);
                        break;

                    case "Day":
                        photoFilter = DayFilter(filter, photoFilter);
                        break;

                    case "Month":
                        photoFilter = MonthFilter(filter, photoFilter);
                        break;

                    case "Tag":
                        photoFilter = TagFilter(filter, photoFilter);
                        break;
                    
                    case "CameraModel":
                        photoFilter = StringFilter(filter, photoFilter);
                      
                        break;

                    case "Path":
                        photoFilter = PathFilter(filter, photoFilter);
                        break;
                 /*   case "Subject":
                        photoFilter = SubjectFilter(filter, photoFilter);
                        break;
                    case "IsoSpeed":
                        returnCollection = IsoSpeedFilter(filter, returnCollection);
                        break;
                    case "LensAperture":
                        returnCollection = LensApertureFilter(filter, returnCollection);
                        break;
                    case "FocalLength":
                        returnCollection = FocalLengthFilter(filter, returnCollection);
                        break;
                    case "ExposureTime":
                        returnCollection = ExposureTimeFilter(filter, returnCollection);
                        break;*/
                }
            }
             
            return photoCollection.Where(photoFilter.Compile()).ToList();

        }

        public string BuildFilter()
        {
            string filterString = "";
            string returnString="";


            foreach (FilterSetting filter in filters)
            {
                switch (filter.ColumnName)
                {
                    case "Tag":
                        returnString = TagFilter(filter);
                        break;
                    case "Subject":
                    case "Title":
                    case "Comment":
                    case "CameraModel":
                        returnString = TextFieldFilter(filter);
                        break;
                    
                    case "Year":
                        returnString = YearFilter(filter);
                        break;
                    case "Month":
                        returnString = MonthFilter(filter);
                        break;
                    case "Day":
                        returnString = DayFilter(filter);
                        break;
                    case "Path":
                        returnString = PathFilter(filter);
                        break;
                    case "PhotoSetName":
                        returnString = PhotoSetNameFilter(filter);
                        break;


                    default:
                        returnString = ColumnFilter(filter);
                        break;


                }
                if (filterString == "")
                {
                    filterString = returnString;// filter.ToSQL();
                }
                else
                {
                    filterString = filterString + " AND " + returnString;// filter.ToSQL();
                }
            }

            return filterString;


        }
    }
}
